package com.tsing.cli.common.config;

import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.connection.DataType;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * Redis管理类
 *
 * @author TheTsing
 */
@Component
@RequiredArgsConstructor
public final class RedisManager {

    private final RedisTemplate<String, Object> redisTemplate;

    // ------------------------------------------------------------------------ 通用

    /**
     * 删除单个键值
     *
     * @param key 键
     * @return 是否成功
     */
    public boolean delete(String key) {
        return redisTemplate.delete(key);
    }

    /**
     * 批量删除键值
     *
     * @param keys 键集合
     * @return 删除成功的数量
     */
    public long delete(Collection<String> keys) {
        return redisTemplate.delete(keys);
    }

    /**
     * 是否存在键所对应的值
     *
     * @param key 键
     * @return 是否存在
     */
    public boolean hasKey(String key) {
        return redisTemplate.hasKey(key);
    }

    /**
     * 根据键前缀查找键集合
     *
     * @param keyPrefix 键前缀
     * @return 键集合
     */
    public Set<String> getByKeyPrefix(String keyPrefix) {
        Set<String> keys = new HashSet<>();
        Cursor<String> cursor = redisTemplate.scan(ScanOptions.scanOptions().match(keyPrefix + "*").count(Integer.MAX_VALUE).build());
        while (cursor.hasNext()) {
            keys.add(cursor.next());
        }
        return keys;
    }

    /**
     * 设置过期时间
     *
     * @param key      键
     * @param timeout  过期时间
     * @param timeUnit 过期时间单位
     * @return 是否成功
     */
    public boolean expire(String key, long timeout, TimeUnit timeUnit) {
        return redisTemplate.expire(key, timeout, timeUnit);
    }

    /**
     * 设置过期时间
     *
     * @param key  键
     * @param date 过期时间
     * @return 是否成功
     */
    public boolean expire(String key, Date date) {
        return redisTemplate.expireAt(key, date);
    }

    /**
     * 获取剩余过期时间
     *
     * @param key 键
     * @return 过期时间/秒
     */
    public long getExpire(String key) {
        return redisTemplate.getExpire(key);
    }

    /**
     * 获取剩余过期时间
     *
     * @param key      键
     * @param timeUnit 过期时间单位
     * @return 过期时间
     */
    public long getExpire(String key, TimeUnit timeUnit) {
        return redisTemplate.getExpire(key, timeUnit);
    }

    /**
     * 持久化该键值
     *
     * @param key 键
     * @return 是否成功
     */
    public boolean persist(String key) {
        return redisTemplate.persist(key);
    }

    /**
     * 获取键所对应的值的类型
     *
     * @param key 键
     * @return 值的类型
     */
    public DataType type(String key) {
        return redisTemplate.type(key);
    }

    // ------------------------------------------------------------------------ String

    /**
     * 新增一个字符串
     *
     * @param key   键
     * @param value 值
     */
    public void setString(String key, Object value) {
        redisTemplate.opsForValue().set(key, value);
    }

    /**
     * 新增一个字符串
     *
     * @param key           键
     * @param value         值
     * @param timeoutSecond 过期时间/秒
     */
    public void setString(String key, Object value, long timeoutSecond) {
        if (timeoutSecond > 0) {
            redisTemplate.opsForValue().set(key, value, timeoutSecond, TimeUnit.SECONDS);
        } else {
            setString(key, value);
        }
    }

    /**
     * 新增一个字符串
     *
     * @param key      键
     * @param value    值
     * @param timeout  过期时间
     * @param timeUnit 过期时间单位
     */
    public void setString(String key, Object value, long timeout, TimeUnit timeUnit) {
        redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
    }

    /**
     * 如果键不存在则新增，存在则不改变已经有的值
     *
     * @param key   键
     * @param value 值
     * @return 是否成功
     */
    public boolean setStringIfAbsent(String key, Object value) {
        return redisTemplate.opsForValue().setIfAbsent(key, value);
    }

    /**
     * 如果键不存在则新增，存在则不改变已经有的值
     *
     * @param key      键
     * @param value    值
     * @param timeout  过期时间
     * @param timeUnit 过期时间单位
     * @return 是否成功
     */
    public boolean setStringIfAbsent(String key, Object value, long timeout, TimeUnit timeUnit) {
        return redisTemplate.opsForValue().setIfAbsent(key, value, timeout, timeUnit);
    }

    /**
     * 如果键存在则更新，不存在则无效
     *
     * @param key   键
     * @param value 值
     * @return 是否成功
     */
    public boolean setStringIfPresent(String key, Object value) {
        return redisTemplate.opsForValue().setIfPresent(key, value);
    }

    /**
     * 如果键存在则更新，不存在则无效
     *
     * @param key      键
     * @param value    值
     * @param timeout  过期时间
     * @param timeUnit 过期时间单位
     * @return 是否成功
     */
    public boolean setStringIfPresent(String key, Object value, long timeout, TimeUnit timeUnit) {
        return redisTemplate.opsForValue().setIfPresent(key, value, timeout, timeUnit);
    }

    /**
     * 批量新增字符串
     *
     * @param map 键值对
     */
    public void multiSetString(Map<String, Object> map) {
        redisTemplate.opsForValue().multiSet(map);
    }

    /**
     * 批量新增字符串，如果键不存在则新增，存在则不改变已经有的值（只要map中有一个key存在，则都不做修改）
     *
     * @param map 键值对
     * @return 是否成功
     */
    public boolean multiSetStringIfAbsent(Map<String, Object> map) {
        return redisTemplate.opsForValue().multiSetIfAbsent(map);
    }

    /**
     * 通过Pipelined批量新增字符串
     *
     * @param map 键值对
     */
    public void multiSetStringByPipelined(Map<String, String> map) {
        RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
        redisTemplate.executePipelined((RedisConnection redisConnection) -> {
            map.forEach((k, v) -> redisConnection.stringCommands().set(serializer.serialize(k), serializer.serialize(v)));
            return null;
        });
    }

    /**
     * 获取键对应的值
     *
     * @param key 键
     * @return 值
     */
    public Object getString(String key) {
        return redisTemplate.opsForValue().get(key);
    }

    /**
     * 获取键对应的值并删除该字符串
     *
     * @param key 键
     * @return 值
     */
    public Object getAndDeleteString(String key) {
        return redisTemplate.opsForValue().getAndDelete(key);
    }

    /**
     * 获取键对应的值并重设过期时间
     *
     * @param key      键
     * @param timeout  过期时间
     * @param timeUnit 过期时间单位
     * @return 值
     */
    public Object getAndExpireString(String key, long timeout, TimeUnit timeUnit) {
        return redisTemplate.opsForValue().getAndExpire(key, timeout, timeUnit);
    }

    /**
     * 获取键对应的值并持久化
     *
     * @param key 键
     * @return 值
     */
    public Object getAndPersistString(String key) {
        return redisTemplate.opsForValue().getAndPersist(key);
    }

    /**
     * 获取键对应的值并重新赋值（会清除过期时间）
     *
     * @param key   键
     * @param value 新值
     * @return 值
     */
    public Object getAndSetString(String key, Object value) {
        return redisTemplate.opsForValue().getAndSet(key, value);
    }

    /**
     * 批量获取字符串
     *
     * @param keys 键集合
     * @return 值集合
     */
    public List<Object> multiGetString(List<String> keys) {
        return redisTemplate.opsForValue().multiGet(keys);
    }

    /**
     * 通过Pipelined批量获取字符串
     *
     * @param keys 键集合
     * @return 值集合
     */
    public List<Object> multiGetStringByPipelined(List<String> keys) {
        RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
        return redisTemplate.executePipelined((RedisConnection redisConnection) -> {
            keys.forEach(item -> redisConnection.stringCommands().get(serializer.serialize(item)));
            return null;
        }, serializer);
    }

    /**
     * 将键对应的值递增1（如果key不存在，那么key的值会先被初始化为0，然后再执行increment命令；如果值包含错误的类型，或字符串类型的值不能表示为数字，那么返回一个错误）
     *
     * @param key 键
     * @return 递增后的值
     */
    public long incrementString(String key) {
        return redisTemplate.opsForValue().increment(key);
    }

    /**
     * 将键对应的值递增delta（如果key不存在，那么key的值会先被初始化为0，然后再执行increment命令；如果值包含错误的类型，或字符串类型的值不能表示为数字，那么返回一个错误）
     *
     * @param key   键
     * @param delta 增量
     * @return 递增后的值
     */
    public long incrementString(String key, long delta) {
        return redisTemplate.opsForValue().increment(key, delta);
    }

    /**
     * 将键对应的值递增delta（如果key不存在，那么key的值会先被初始化为0，然后再执行increment命令；如果值包含错误的类型，或字符串类型的值不能表示为数字，那么返回一个错误）
     *
     * @param key   键
     * @param delta 增量
     * @return 递增后的值
     */
    public double incrementString(String key, double delta) {
        return redisTemplate.opsForValue().increment(key, delta);
    }

    /**
     * 将键对应的值递减1（如果key不存在，那么key的值会先被初始化为0，然后再执行decrement命令；如果值包含错误的类型，或字符串类型的值不能表示为数字，那么返回一个错误）
     *
     * @param key 键
     * @return 递减后的值
     */
    public long decrementString(String key) {
        return redisTemplate.opsForValue().decrement(key);
    }

    /**
     * 将键对应的值递减delta（如果key不存在，那么key的值会先被初始化为0，然后再执行decrement命令；如果值包含错误的类型，或字符串类型的值不能表示为数字，那么返回一个错误）
     *
     * @param key   键
     * @param delta 递减量
     * @return 递减后的值
     */
    public long decrementString(String key, long delta) {
        return redisTemplate.opsForValue().decrement(key, delta);
    }

    /**
     * 在原有值的基础上追加字符串到末尾
     *
     * @param key   键
     * @param value 追加的字符串
     * @return 追加后的字符串长度
     */
    public int appendString(String key, String value) {
        return redisTemplate.opsForValue().append(key, value);
    }

    // ------------------------------------------------------------------------ List

    /**
     * 获取list集合指定范围内的元素
     *
     * @param key   键
     * @param start 开始位置，0是开始位置
     * @param end   结束位置，-1返回所有
     * @return 元素集合
     */
    public List<Object> getList(String key, long start, long end) {
        return redisTemplate.opsForList().range(key, start, end);
    }

    /**
     * 获取list集合中的所有元素
     *
     * @param key 键
     * @return 元素集合
     */
    public List<Object> getList(String key) {
        return redisTemplate.opsForList().range(key, 0, -1);
    }

    /**
     * 获取list集合中指定索引位置的元素
     *
     * @param key   键
     * @param index 索引
     * @return 元素
     */
    public Object getList(String key, long index) {
        return redisTemplate.opsForList().index(key, index);
    }

    /**
     * 通过Pipelined批量获取list集合
     *
     * @param keys 键集合
     * @return 值集合
     */
    public List<Object> multiGetListByPipelined(List<String> keys) {
        RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
        return redisTemplate.executePipelined((RedisConnection redisConnection) -> {
            keys.forEach(item -> redisConnection.listCommands().lRange(serializer.serialize(item), 0, -1));
            return null;
        }, serializer);
    }

    /**
     * 获取list集合的长度
     *
     * @param key 键
     * @return 集合大小
     */
    public long listSize(String key) {
        return redisTemplate.opsForList().size(key);
    }

    /**
     * 存储在list集合的头部，即把它放在最前面的索引处
     *
     * @param key   键
     * @param value 元素
     * @return 集合大小
     */
    public long leftPushList(String key, Object value) {
        return redisTemplate.opsForList().leftPush(key, value);
    }

    /**
     * 存储在list集合的头部，即把它放在最前面的索引处
     *
     * @param key    键
     * @param values 元素集合
     * @return 集合大小
     */
    public long leftPushList(String key, List<Object> values) {
        return redisTemplate.opsForList().leftPushAll(key, values);
    }

    /**
     * 如果pivot值存在，则在pivot元素前面添加
     *
     * @param key   键
     * @param pivot 元素
     * @param value 元素
     * @return 集合大小
     */
    public long leftPushList(String key, Object pivot, Object value) {
        return redisTemplate.opsForList().leftPush(key, pivot, value);
    }

    /**
     * 如果list集合存在，则将元素存储在list集合的头部，即把它放在最前面的索引处
     *
     * @param key   键
     * @param value 元素
     * @return 集合大小
     */
    public long leftPushListIfPresent(String key, Object value) {
        return redisTemplate.opsForList().leftPushIfPresent(key, value);
    }

    /**
     * 存储在list集合的尾部，即把它放在最后面
     *
     * @param key   键
     * @param value 元素
     * @return 集合大小
     */
    public long rightPushList(String key, Object value) {
        return redisTemplate.opsForList().rightPush(key, value);
    }

    /**
     * 存储在list集合的尾部，即把它放在最后面
     *
     * @param key    键
     * @param values 元素集合
     * @return 集合大小
     */
    public long rightPushList(String key, List<Object> values) {
        return redisTemplate.opsForList().rightPushAll(key, values);
    }

    /**
     * 如果pivot值存在，则在pivot元素后面添加
     *
     * @param key   键
     * @param pivot 元素
     * @param value 元素
     * @return 集合大小
     */
    public long rightPushList(String key, Object pivot, Object value) {
        return redisTemplate.opsForList().rightPush(key, pivot, value);
    }

    /**
     * 如果list集合存在，则将元素存储在list集合的尾部，即把它放在最后面
     *
     * @param key   键
     * @param value 元素
     * @return 集合大小
     */
    public long rightPushListIfPresent(String key, Object value) {
        return redisTemplate.opsForList().rightPushIfPresent(key, value);
    }

    /**
     * 设置指定索引处元素的值
     *
     * @param key   键
     * @param index 索引
     * @param value 值
     */
    public void setList(String key, long index, Object value) {
        redisTemplate.opsForList().set(key, index, value);
    }

    // ------------------------------------------------------------------------ Set

    /**
     * 添加元素到set集合
     *
     * @param key    键
     * @param values 元素或元素数组
     * @return 添加成功的数量
     */
    public long addSet(String key, Object... values) {
        return redisTemplate.opsForSet().add(key, values);
    }

    /**
     * 从set集合中移除元素
     *
     * @param key    键
     * @param values 元素或元素数组
     * @return 移除成功的数量
     */
    public long removeSet(String key, Object... values) {
        return redisTemplate.opsForSet().remove(key, values);
    }

    /**
     * 获取set集合的长度
     *
     * @param key 键
     * @return 集合大小
     */
    public long setSize(String key) {
        return redisTemplate.opsForSet().size(key);
    }

    /**
     * set集合中是否存在value
     *
     * @param key   键
     * @param value 元素
     * @return 是否存在
     */
    public boolean isSetMember(String key, Object value) {
        return redisTemplate.opsForSet().isMember(key, value);
    }

    /**
     * set集合中是否存在value
     *
     * @param key   键
     * @param value 元素或元素数组
     * @return 结果集
     */
    public Map<Object, Boolean> isSetMember(String key, Object... value) {
        return redisTemplate.opsForSet().isMember(key, value);
    }

    /**
     * 获取set集合中的所有元素
     *
     * @param key 键
     * @return 元素集合
     */
    public Set<Object> getSet(String key) {
        return redisTemplate.opsForSet().members(key);
    }

    /**
     * 通过Pipelined批量获取set集合
     *
     * @param keys 键集合
     * @return 值集合
     */
    public List<Object> multiGetSetByPipelined(List<String> keys) {
        RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
        return redisTemplate.executePipelined((RedisConnection redisConnection) -> {
            keys.forEach(item -> redisConnection.setCommands().sMembers(serializer.serialize(item)));
            return null;
        }, serializer);
    }

    // ------------------------------------------------------------------------ hash

    /**
     * 删除hash表字段
     *
     * @param key      键
     * @param hashKeys hashKey或hashKey数组
     * @return 删除成功的数量
     */
    public long deleteHash(String key, Object... hashKeys) {
        return redisTemplate.opsForHash().delete(key, hashKeys);
    }

    /**
     * hash表中指定字段是否存在
     *
     * @param key     键
     * @param hashKey hashKey
     * @return 是否存在
     */
    public boolean isHashKey(String key, Object hashKey) {
        return redisTemplate.opsForHash().hasKey(key, hashKey);
    }

    /**
     * 获取hash表中所有的hashKey
     *
     * @param key 键
     * @return hashKey集合
     */
    public Set<Object> getHashKey(String key) {
        return redisTemplate.opsForHash().keys(key);
    }

    /**
     * 获取hash表中所有的值
     *
     * @param key 键
     * @return 值集合
     */
    public List<Object> getHash(String key) {
        return redisTemplate.opsForHash().values(key);
    }

    /**
     * 获取hash表中所有的键值对
     *
     * @param key 键
     * @return map集合
     */
    public Map<Object, Object> getHashMap(String key) {
        return redisTemplate.opsForHash().entries(key);
    }

    /**
     * 获取hash表字段
     *
     * @param key     键
     * @param hashKey hashKey
     * @return 值
     */
    public Object getHash(String key, Object hashKey) {
        return redisTemplate.opsForHash().get(key, hashKey);
    }

    /**
     * 批量获取hash表字段
     *
     * @param key      键
     * @param hashKeys hashKey集合
     * @return 值集合
     */
    public List<Object> multiGetHash(String key, Collection<Object> hashKeys) {
        return redisTemplate.opsForHash().multiGet(key, hashKeys);
    }

    /**
     * 通过Pipelined批量获取hash表
     *
     * @param keys 键集合
     * @return 结果集
     */
    public List<Object> multiGetHashByPipelined(List<String> keys) {
        RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
        return redisTemplate.executePipelined((RedisConnection redisConnection) -> {
            keys.forEach(item -> redisConnection.hashCommands().hGetAll(serializer.serialize(item)));
            return null;
        }, serializer);
    }

    /**
     * 将键对应的值递增delta（如果key不存在，那么key的值会先被初始化为0，然后再执行increment命令；如果值包含错误的类型，或字符串类型的值不能表示为数字，那么返回一个错误）
     *
     * @param key     键
     * @param hashKey hashKey
     * @param delta   增量
     * @return 递增后的值
     */
    public long incrementHash(String key, Object hashKey, long delta) {
        return redisTemplate.opsForHash().increment(key, hashKey, delta);
    }

    /**
     * 将键对应的值递增delta（如果key不存在，那么key的值会先被初始化为0，然后再执行increment命令；如果值包含错误的类型，或字符串类型的值不能表示为数字，那么返回一个错误）
     *
     * @param key     键
     * @param hashKey hashKey
     * @param delta   增量
     * @return 递增后的值
     */
    public double incrementHash(String key, Object hashKey, double delta) {
        return redisTemplate.opsForHash().increment(key, hashKey, delta);
    }

    /**
     * 获取hash表中键值对的数量
     *
     * @param key 键
     * @return hash表大小
     */
    public long hashSize(String key) {
        return redisTemplate.opsForHash().size(key);
    }

    /**
     * 新增键值对
     *
     * @param key     键
     * @param hashKey hashKey
     * @param value   值
     */
    public void putHash(String key, String hashKey, Object value) {
        redisTemplate.opsForHash().put(key, hashKey, value);
    }

    /**
     * 以map集合的形式添加键值对
     *
     * @param key 键
     * @param map map集合
     */
    public void putHash(String key, Map<String, Object> map) {
        redisTemplate.opsForHash().putAll(key, map);
    }

    /**
     * 如果hashKey不存在，则添加键值对
     *
     * @param key     键
     * @param hashKey hashKey
     * @param value   值
     * @return 是否成功
     */
    public boolean putHashIfAbsent(String key, String hashKey, Object value) {
        return redisTemplate.opsForHash().putIfAbsent(key, hashKey, value);
    }

    /**
     * 通过Pipelined批量新增键值对
     *
     * @param key 键
     * @param map 键值对
     */
    public void multiSetHashByPipelined(String key, Map<String, String> map) {
        RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
        redisTemplate.executePipelined((RedisConnection redisConnection) -> {
            map.forEach((k, v) -> redisConnection.hashCommands().hSet(serializer.serialize(key), serializer.serialize(k), serializer.serialize(v)));
            return null;
        });
    }

}
